package br.com.yahoo.mau_mss.tutorlucene.service.impl;

import java.io.File;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.Field.Index;
import org.apache.lucene.document.Field.Store;
import org.apache.lucene.document.Field.TermVector;
import org.apache.lucene.index.CorruptIndexException;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.IndexWriterConfig.OpenMode;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.LockObtainFailedException;
import org.apache.lucene.util.Version;
import org.springframework.stereotype.Service;
import br.com.yahoo.mau_mss.tutorlucene.model.Curriculum;
import br.com.yahoo.mau_mss.tutorlucene.service.CurriculumException;
import br.com.yahoo.mau_mss.tutorlucene.service.CurriculumService;
import br.com.yahoo.mau_mss.tutorlucene.util.Constants;

/**
 * Implementação de CurriculumService.
 * 
 * @author Paulo Sigrist / Wilson A. Higashino
 * 
 */
@Service("curriculumServiceBean")
public class CurriculumServiceBean extends LuceneBean implements
    CurriculumService {
  private static final Logger log = Logger.getLogger(CurriculumServiceBean.class.getName());
  
  public CurriculumServiceBean() {
    log.info("Criou CurriculumServiceBean");
  }

  /**
   * {@inheritDoc}
   * 
   * Salva o arquivo na pasta de armazenamento de arquivos.
   * O arquivo do currículo é salvo da seguinte forma:
   * <ol>
   * <li>A primeira linha contém o nome do candidato</li>
   * <li>A segunda linha contém o email do candidato</li>
   * <li>A terceira linha em dianta, contém o conteúdo do currículo</li>
   * </ol>
   * O nome do arquivo é formado pela quantidade de arquivos na pasta de
   * armazenamento + 1, adicionado a extensão '.txt'.
   * Após armazenar o arquivo em disco, o engine de indexação é chamado para
   * indexar o novo currículo.
   * @see ConfigBean#getFilesDirectory()
   * @see #getNextId()
   * @see #index(Curriculum, File)
   */
  @Override
  public void addCurriculum(Curriculum curriculum) throws CurriculumException {
    if (curriculum == null) { 
      throw new CurriculumException("Curriculum cannot be null"); 
    }
    // Salva o arquivo em disco
    PrintWriter writer = null;
    try {
      File file = new File(this.configBean.getFilesDirectory(), getNextId() + ".txt");
      writer = new PrintWriter(new OutputStreamWriter(new FileOutputStream(
          file), "UTF-8"));
      writer.println(curriculum.getName());
      writer.println(curriculum.getEmail());
      writer.println(curriculum.getContent());
      // Chama a indexação - comentado para utilizar indexação disparada pelo scheduleBean
      //index(curriculum, file);
      
    } catch (IOException e) {
      // Caso ocorra algum erro, lançar uma exception
      log.log(Level.SEVERE, "Erro de IO ao salvar arquivo", e);
      throw new CurriculumException("Erro ao salvar arquivo.", e);
    } finally {
      // Finalmente, fechar o arquivo
      if (writer != null) {
        writer.close();
      }
    }

  }

  /**
   * @return Retorna a quantidade de arquivos existentes na pasta de
   *         armazenamento de currículos, mais um.
   * 
   * @see ConfigBean#getFilesDirectory()
   */
  private int getNextId() {
    // Cria um File apontando para a pasta de armazenamento de currículos
    File file = new File(configBean.getFilesDirectory());

    // Verifica se a pasta existe
    if (file.exists()) {
      // Obtém a quantidade de arquivos mais um
      return (file.list().length + 1);
    }
    return 0;
  }

  /**
   * {@inheritDoc}
   * Procura por todos os arquivos na pasta de armazenamento de currículos e
   * indexa o arquivo
   * @see #loadCurriculum(File)
   * @see ConfigBean#getFilesDirectory()
   * @see ConfigBean#getFiles
   * @see Directory
   * @see Analyzer
   * @see IndexWriterConfig
   * @see IndexWriter
   * @see Document
   */
  @Override
  public void indexAll() throws CurriculumException {
    // Obtém os arquivos da pasta de armazenamento de currículos
    // filtrando por .txt
    File file = new File(this.configBean.getFilesDirectory());
    File files[] = file.listFiles(new FilenameFilter() {
      @Override
      public boolean accept(File dir, String name) {
        return name.endsWith(".txt");
      }
    });
    if (files == null) {
      log.log(Level.WARNING, "N\u00e3o achou nenhum arquivo em {0}", configBean.getFilesDirectory());
      return;
    }
    log.info("-------------------------------------------------------\n" + 
             "Recriando todo índice\n" + 
             "-------------------------------------------------------");
    IndexWriterConfig indexConfig = new IndexWriterConfig(Version.LUCENE_35,
        analyzer);
    // Informar que queremos recriar o índice
    indexConfig.setOpenMode(OpenMode.CREATE);
    IndexWriter indexWriter = null;
    try {
      indexWriter = new IndexWriter(directory, indexConfig);
    } catch (CorruptIndexException e) {
      throw new CurriculumException("Índice corrompido.", e);
    } catch (LockObtainFailedException e) {
      throw new CurriculumException("Índice já está sendo utilizado.", e);
    } catch (IOException e) {
      throw new CurriculumException("Erro ao atualizar índice.", e);
    }
    // Para cada arquivo encontrado, carregue o currículo e adicione no
    // indexador
    for (File fileItem : files) {
      try {
        // Carrega o currículo
        Curriculum curriculum = this.loadCurriculum(fileItem);
        // Adiciona no indexador
        indexWriter.addDocument(createDocument(curriculum, fileItem.getName()));
      } catch (CorruptIndexException e) {
        throw new CurriculumException("Índice corrompido.", e);
      } catch (IOException e) {
        throw new CurriculumException("Erro ao atualizar índice.", e);
      }
    }
    try {
      indexWriter.close();
    } catch (CorruptIndexException e) {
      throw new CurriculumException("Índice corrompido.", e);
    } catch (IOException e) {
      throw new CurriculumException("Erro ao atualizar índice.", e);
    }
  }

  /**
   * {@inheritDoc}
   * 
   * @see Directory
   * @see Analyzer
   * @see IndexWriterConfig
   * @see IndexWriter
   * @see Document
   */
  @Override
  public void index(Curriculum curriculum, File file) throws CurriculumException {
    log.info("-------------------------------------------------------\n" + 
             "Indexando um item\n" + 
             "-------------------------------------------------------");
    IndexWriterConfig indexConfig = new IndexWriterConfig(Version.LUCENE_35,
        analyzer);
    try {
      IndexWriter indexWriter = new IndexWriter(this.directory, indexConfig);
      indexWriter.addDocument(createDocument(curriculum, file.getName()));
      indexWriter.close();
    } catch (CorruptIndexException e) {
      throw new CurriculumException("Índice corrompido.", e);
    } catch (LockObtainFailedException e) {
      throw new CurriculumException("Índice já está sendo utilizado.", e);
    } catch (IOException e) {
      throw new CurriculumException("Erro ao atualizar índice.", e);
    }
  }

  /**
   * Cria um {@link Document} baseado nas informações do currículo e do
   * arquivo onde ele está armazenado.
   * 
   * @param curriculum - Currículo a ser indexado
   * @param fileName - Arquivo onde o currículo está armazenado
   * @return Uma instância de Document com os dados do currículo
   * @throws IOException
   * @see Document
   * @See Field
   * @See Index
   */
  private Document createDocument(Curriculum curriculum, String fileName) throws IOException {
    Document document = new Document();
    // Adicionar o campo nome - Index.NOT_ANALYZED_NO_NORMS indica que o nome não
    // será analisado (a busca tem que ser exata ou com coringas)
    document.add(new Field(Constants.FIELD_NAME, curriculum.getName(), Store.YES,
        Index.NOT_ANALYZED_NO_NORMS));
    // Adicionar o campo email
    document.add(new Field(Constants.FIELD_EMAIL, curriculum.getEmail(), Store.YES,
        Index.NOT_ANALYZED_NO_NORMS));
    // Adicionar o campo file
    document.add(new Field(Constants.FIELD_FILE, fileName, Store.YES,
        Index.NOT_ANALYZED_NO_NORMS));
    // Adicionar o conteúdo do currículo - Store.NO indica que o conteúdo não
    // será adicionado na estrutura do índice, apesar de permitir buscar por ele
    // - Index.NOT_ANALYZED indica que o conteúdo será quebrado em tokens
    document.add(new Field(Constants.FIELD_CONTENT, curriculum.getContent(), Store.NO,
        Index.ANALYZED, TermVector.WITH_POSITIONS_OFFSETS));
    return document;
  }

}
